package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"sync"
	"time"
)

type LodgerTime time.Time

func (c *LodgerTime) UnmarshalJSON(b []byte) error {
	return nil
}

func (c LodgerTime) MarshalJSON() ([]byte, error) {
	return json.Marshal(time.Time(c))
}

func doRequest(method string, cmd string, data any) io.ReadCloser {

	url := "http://localhost:5000/book1/api/"
	var jdata []byte
	var pdata io.Reader

	if data != nil {
		jdata, _ = json.Marshal(data)
		pdata = bytes.NewReader(jdata)
	}

	req, err := http.NewRequest(method, url+cmd, pdata)
	if err != nil {
		log.Printf("Request error: %s\n", err)
	}
	if jdata != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Printf(method+" error: %s\n", err)
	}

	if res.ContentLength != 0 {
		return res.Body
	}

	res.Body.Close()
	return nil
}

func doRequest2(method string, cmd string, data any) io.ReadCloser {

	result := make(chan io.ReadCloser)

	go func(result chan io.ReadCloser) {
		url := "http://localhost:5000/book1/api/"
		var jdata []byte
		var pdata io.Reader
		if data != nil {
			jdata, _ = json.Marshal(data)
			pdata = bytes.NewReader(jdata)
		}

		req, err := http.NewRequest(method, url+cmd, pdata)
		if err != nil {
			log.Printf("Request error: %s\n", err)
		}
		if jdata != nil {
			req.Header.Set("Content-Type", "application/json")
		}

		res, err := http.DefaultClient.Do(req)
		if err != nil {
			log.Printf(method+" error: %s\n", err)
		}
		if res.ContentLength != 0 {
			result <- res.Body
		} else {
			res.Body.Close()
			result <- nil
		}
	}(result)

	return <-result
}

func doRequest3(method string, cmd string, data any) io.ReadCloser {

	result := make(chan io.ReadCloser)

	go func(result chan<- io.ReadCloser) {
		url := "http://localhost:5000/book1/api/"
		var jdata []byte
		var pdata io.Reader
		if data != nil {
			jdata, _ = json.Marshal(data)
			pdata = bytes.NewReader(jdata)
		}

		req, err := http.NewRequest(method, url+cmd, pdata)
		if err != nil {
			log.Printf("Request error: %s\n", err)
		}
		if jdata != nil {
			req.Header.Set("Content-Type", "application/json")
		}

		res, err := http.DefaultClient.Do(req)
		if err != nil {
			log.Printf(method+" error: %s\n", err)
		}
		if res.ContentLength != 0 {
			result <- res.Body
		} else {
			res.Body.Close()
			result <- nil
		}
	}(result)

	select {
	case res := <-result:
		return res
	case <-time.After(2 * time.Second):
		log.Println("Request timeout")
		return nil
	}
}

func getDecoded(body io.ReadCloser, result any) bool {
	if body != nil {
		json.NewDecoder(body).Decode(&result)
		body.Close()
		return true
	}
	return false
}

func getAny(body io.ReadCloser) any {
	var result any
	if getDecoded(body, &result) {
		return result
	}
	return nil
}

func getBytes(body io.ReadCloser) []byte {
	if body != nil {
		defer body.Close()
		resBody, _ := io.ReadAll(body)
		return resBody
	}
	return nil
}

type lodgerItem struct {
	Id    int        `json:"id"`
	Fio   string     `json:"fio"`
	Room  string     `json:"room"`
	Count string     `json:"count"`
	Child string     `json:"child"`
	Time  LodgerTime `json:"time"`
}

func (item *lodgerItem) input() {
	item.Time = LodgerTime(time.Now())
	fmt.Print("Count: ")
	fmt.Scanln(&item.Count)
	fmt.Print("Fio: ")
	fmt.Scanln(&item.Fio)
	fmt.Print("Room: ")
	fmt.Scanln(&item.Room)
	fmt.Print("Child: ")
	fmt.Scanln(&item.Child)
}

func (item lodgerItem) output() {
	fmt.Println("Id:", item.Id)
	fmt.Println("Count:", item.Count)
	fmt.Println("Fio:", item.Fio)
	fmt.Println("Room:", item.Room)
	fmt.Println("Child:", item.Child)
	//	fmt.Println("Time:", item.Time)
}

func (item *lodgerItem) send() {
	if item.Id > 0 {
		doRequest("PUT", strconv.Itoa(item.Id), item)
	} else {
		doRequest("POST", "", item)
	}
}

func getItem(id int) *lodgerItem {
	bytes := getBytes(doRequest("GET", strconv.Itoa(id), nil))
	fmt.Println(string(bytes))

	var item *lodgerItem
	//	json.Unmarshal(bytes, &item)
	getDecoded(doRequest3("GET", strconv.Itoa(id), nil), &item)

	return item
}

/*
*

	func show() {
		ids := getAny(doRequest2("GET", "", nil)).(map[string]interface{})["ids"]

		for _, id := range ids.([]any) {
			fmt.Println(id.([]any)[0], id.([]any)[1])
			item := getItem(int(id.([]any)[0].(float64)))
			item.output()
			fmt.Println("---")
		}
	}

*
*/
func show() {
	result := getAny(doRequest2("GET", "", nil))
	if result != nil {
		ids, ok := result.(map[string]interface{})["ids"]
		if ok && ids != nil {
			for _, id := range ids.([]interface{}) {
				itemID := int(id.(float64))
				fmt.Println(itemID)
				item := getItem(itemID)
				item.output()
				fmt.Println("---")
			}
		} else {
			fmt.Println("No 'ids' in the response.")
		}
	} else {
		fmt.Println("Error fetching data.")
	}
}

func show2() {
	workers := runtime.NumCPU()
	ids := make(chan int, workers)
	done := make(chan struct{}, workers)

	go func() {
		for _, id := range getAny(doRequest2("GET", "", nil)).(map[string]interface{})["ids"].([]any) {
			fmt.Println(id.([]any)[0], id.([]any)[1])
			ids <- int(int(get[float64](id, 0)))
		}
		close(ids)
	}()

	for i := 0; i < workers; i++ {
		go func(i int) {
			for id := range ids {
				fmt.Println(i, "--->>>")
				item := getItem(id)
				item.output()
				fmt.Println(i, "<<<---")
			}
			done <- struct{}{}
		}(i)
	}
	for i := 0; i < workers; i++ {
		<-done
	}
}

func get[T any](v any, i int) T {
	return v.([]any)[i].(T)
}

func getIDs(ids chan<- int) {
	for _, id := range getAny(doRequest2("GET", "", nil)).(map[string]interface{})["ids"].([]any) {
		fmt.Println(id.([]any)[0], id.([]any)[1])
		ids <- int(int(get[float64](id, 0)))
	}
	close(ids)
}

func processIDs(done chan<- struct{}, ids <-chan int, i int, mutex *sync.RWMutex) {
	for id := range ids {
		func() {
			mutex.Lock()
			defer mutex.Unlock()
			fmt.Println(i, "--->>>")
			item := getItem(id)
			item.output()
			fmt.Println(i, "<<<---")
		}()
	}
	done <- struct{}{}
}

func show3() {
	workers := runtime.NumCPU()
	ids := make(chan int, workers)
	done := make(chan struct{}, workers)
	mutex := sync.RWMutex{}

	//	mutex := new(sync.RWMutex)
	//	mutex := &sync.RWMutex{}

	go getIDs(ids)

	for i := 0; i < workers; i++ {
		go processIDs(done, ids, i, &mutex)
	}
	for i := 0; i < workers; i++ {
		<-done
	}
}

func add() {
	//	item := new(lodgerItem)
	item := lodgerItem{}
	item.input()
	item.send()
}

func delete() {
	var id int
	fmt.Print("Id: ")
	fmt.Scanln(&id)
	doRequest("DELETE", strconv.Itoa(id), nil)
}

func main() {

	var menuItems = []struct {
		title string
		f     func()
	}{
		{"Show 1", show},
		{"Show 2", show2},
		{"Show 3", show3},
		{"Add", add},
		{"Delete", delete},
	}

	for {
		for i, m := range menuItems {
			fmt.Println(i+1, m.title)
		}

		var i int
		if _, e := fmt.Scanln(&i); e != nil || i < 1 || i > len(menuItems) {
			return
		}
		menuItems[i-1].f()
	}
}
